#include "GLES2FBORenderTexture.h"
#include "GLES2PixelFormat.h"
#include "LogManager.h"
#include "GLES2HardwarePixelBuffer.h"
#include "GLES2FBOMultiRenderTarget.h"

namespace PVM
{
	GLES2FBORenderTexture::GLES2FBORenderTexture(GLES2FBOManager* manager, const std::string& name,
			const GLES2SurfaceDesc& target, bool writeGamma, uint fsaa) :
	GLES2RenderTexture(name, target, writeGamma, fsaa),
	mFB(manager, fsaa)
	{
		mFB.bindSurface(0, target);
		mWidth = mFB.getWidth();
		mHeight = mFB.getHeight();
	}
	
	void GLES2FBORenderTexture::getCustomAttribute(const std::string& name, void* pData)
	{
		if (name == "FBO")
		{
			*static_cast<GLES2FrameBufferObject**>(pData) = &mFB;
		}
	}
	
	void GLES2FBORenderTexture::swapBuffers(bool waitForVSync)
	{
		mFB.swapBuffers();
	}

	bool GLES2FBORenderTexture::attachDepthBuffer(DepthBuffer* depthBuffer)
	{
		bool result;
		if ((result = GLES2RenderTexture::attachDepthBuffer(depthBuffer)))
			mFB.attachDepthBuffer(depthBuffer);
		
		return result;
	}
	
	void GLES2FBORenderTexture::detachDepthBuffer()
	{
		mFB.detachDepthBuffer();
		GLES2RenderTexture::detachDepthBuffer();
	}
	
	void GLES2FBORenderTexture::_detachDepthBuffer()
	{
		mFB.detachDepthBuffer();
		GLES2RenderTexture::_detachDepthBuffer();
	}
	
	#define PROBE_SIZE 16
	
	static const GLenum stencilFormats[] = 
	{
		GL_NONE,
		GL_STENCIL_INDEX8
	};
	static const size_t stencilBits[] = 
	{
		0, 
		8
	};
	#define STENCILFORMAT_COUNT (sizeof(stencilFormats) / sizeof(GLenum))
	
	static const GLenum depthFormats[] = 
	{
		GL_NONE,
		GL_DEPTH_COMPONENT16,
		GL_DEPTH_COMPONENT24_OES,
		GL_DEPTH24_STENCIL8_OES
	};
	static const size_t depthBits[] =
	{
		0, 16,
		24,
		24
	};
	#define DEPTHFORMAT_COUNT (sizeof(depthFormats) / sizeof(GLenum))
	
	GLES2FBOManager::GLES2FBOManager()
	{
		deleteFBOFormats();
		
		glGenFramebuffers(1, &mTempFBO);
	}
	
	GLES2FBOManager::~GLES2FBOManager()
	{
		if (!mRenderBufferMap.empty())
		{
			LogManager::getSingleton().logMessage("GL ES 2: Warning! GLES2FBOManager destructor called, but not all renderbuffers were released.");
		}
		
		glDeleteFramebuffers(1, &mTempFBO);
	}
	
	GLuint GLES2FBOManager::_tryFormat(GLenum depthFormat, GLenum stencilFormat)
	{
		GLuint status, depthRB = 0, stencilRB = 0;
		
		if (depthFormat != GL_NONE)
		{
			glGenRenderbuffers(1, &depthRB);
			glBindRenderbuffer(GL_RENDERBUFFER, depthRB);
			glRenderbufferStorage(GL_RENDERBUFFER, depthFormat,
								  PROBE_SIZE, PROBE_SIZE);
			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, depthRB);
		}
		
		if (stencilFormat != GL_NONE)
		{
			glGenRenderbuffers(1, &stencilRB);
			glBindRenderbuffer(GL_RENDERBUFFER, stencilRB);
			glRenderbufferStorage(GL_RENDERBUFFER, stencilFormat, PROBE_SIZE, PROBE_SIZE);
			glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
									  GL_RENDERBUFFER, stencilRB);
		}
		
		status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
		
		if (depthRB)
			glDeleteRenderbuffers(1, &depthRB);
		if (stencilRB)
			glDeleteRenderbuffers(1, &stencilRB);
		
		return status == GL_FRAMEBUFFER_COMPLETE;
	}
	
	bool GLES2FBOManager::_tryPackedFormat(GLenum packedFormat)
	{
		GLuint packedRB;
		
		glGenRenderbuffers(1, &packedRB);
		
		glBindRenderbuffer(GL_RENDERBUFFER, packedRB);
		glRenderbufferStorage(GL_RENDERBUFFER, packedFormat, PROBE_SIZE, PROBE_SIZE);
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
								  GL_RENDERBUFFER, packedRB);
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
								  GL_RENDERBUFFER, packedRB);
		GLuint status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
		
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, 0);
		glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, 0);
		glDeleteRenderbuffers(1, &packedRB);
		
		return status == GL_FRAMEBUFFER_COMPLETE;
	}
	
	void GLES2FBOManager::detectFBOFormats()
	{
		GLuint fb, tid;
		GLenum target = GL_TEXTURE_2D;
		
		for (size_t x = 0; x < PF_COUNT; ++x)
		{
			mProps[x].valid = false;
			
			GLenum fmt = GLES2PixelUtil::getGLInternalFormat((PixelFormat)x);
			if (fmt == GL_NONE && x != 0)
				continue;
			
			if (PixelUtil::isCompressed((PixelFormat)x))
				continue;
			
			glGenFramebuffers(1, &fb);
			glBindFramebuffer(GL_FRAMEBUFFER, fb);
			if (fmt != GL_NONE)
			{
				glGenTextures(1, &tid);
				glBindTexture(target, tid);
				glTexParameteri(target, GL_TEXTURE_MAX_LEVEL_APPLE, 0);
				glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
				glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
				glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
				
				glTexImage2D(target, 0, fmt, PROBE_SIZE, PROBE_SIZE, 0, fmt, GL_UNSIGNED_BYTE, 0);
				glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
									   target, tid, 0);
			}
			
			GLuint status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
			if (fmt == GL_NONE || status == GL_FRAMEBUFFER_COMPLETE)
			{
				mProps[x].valid = true;
				StringUtil::StrStreamType str;
				str << "FBO" << PixelUtil::getFormatName((PixelFormat)x)
				<< " depth/stencil support: ";
				
				for (size_t depth = 0; depth < DEPTHFORMAT_COUNT; ++depth)
				{
					if (depthFormats[depth] != GL_DEPTH24_STENCIL8_OES)
					{
						for (size_t stencil = 0; stencil < STENCILFORMAT_COUNT; ++stencil)
						{
							if (_tryFormat(depthFormats[depth], stencilFormats[stencil]))
							{
								str << "D" << depthBits[depth] << "S" << stencilBits[stencil] << " ";
								FormatProperties::Mode mode;
								mode.depth = depth;
								mode.stencil = stencil;
								mProps[x].modes.push_back(mode);
							}
						}
					}
					else
					{
						if (_tryPackedFormat(depthFormats[depth]))
						{
							str << "Packed-D" << depthBits[depth] << "S" << 8 << " ";
							FormatProperties::Mode mode;
							mode.depth = depth;
							mode.stencil = 0;
							mProps[x].modes.push_back(mode);
						}
					}
				}
				LogManager::getSingleton().logMessage(str.str());
			}
			
			glBindFramebuffer(GL_FRAMEBUFFER, 0);
			glDeleteFramebuffers(1, &fb);
			
			if (fmt != GL_NONE)
				glDeleteTextures(1, &tid);
		}
		
		std::string fmtstring;
		for (size_t x = 0; x < PF_COUNT; ++x)
		{
			if (mProps[x].valid)
				fmtstring += PixelUtil::getFormatName((PixelFormat)x) + " ";
		}
		LogManager::getSingleton().logMessage("[GLES2] : Valid FBO targets " + fmtstring);
	}
	
	void GLES2FBOManager::getBestDepthStencil(GLenum internalFormat, GLenum* depthFormat, GLenum* stencilFormat)
	{
		const FormatProperties& props = mProps[internalFormat];
		size_t bestmode = 0;
		int bestscore = -1;
		for (size_t mode = 0; mode < props.modes.size(); mode++)
		{
			int desirability = 0;
			
			if (props.modes[mode].stencil)
				desirability += 1000;
			if (props.modes[mode].depth)
				desirability += 2000;
			if (depthBits[props.modes[mode].depth] == 24)
				desirability += 500;
			if (depthFormats[props.modes[mode].depth] == GL_DEPTH24_STENCIL8_OES)
				desirability += 5000;
			desirability += stencilBits[props.modes[mode].stencil] + depthBits[props.modes[mode].depth];
			
			if (desirability > bestscore)
			{
				bestscore = desirability;
				bestmode = mode;
			}
		}
		*depthFormat = depthFormats[props.modes[bestmode].depth];
		*stencilFormat = stencilFormats[props.modes[bestmode].stencil];
	}
	
	GLES2FBORenderTexture* GLES2FBOManager::createRenderTexture(const std::string& name,
					const GLES2SurfaceDesc& target, bool writeGamma, uint fsaa)
	{
		GLES2FBORenderTexture* retval = new GLES2FBORenderTexture(this, name, target, writeGamma, fsaa);
		return retval;
	}
	
	MultiRenderTarget* GLES2FBOManager::createMultiRenderTarget(const std::string& name)
	{
		return new GLES2FBOMultiRenderTarget(this, name);
	}
	
	void GLES2FBOManager::bind(RenderTarget* target)
	{
		GLES2FrameBufferObject* fbo = 0;
		target->getCustomAttribute("FBO", &fbo);
		if (fbo)
			fbo->bind();
		else
			glBindFramebuffer(GL_FRAMEBUFFER, 1);
	}
	
	GLES2SurfaceDesc GLES2FBOManager::requestRenderBuffer(GLenum format, size_t width, size_t height, uint fsaa)
	{
		GLES2SurfaceDesc retval;
		retval.buffer = 0;
		if (format != GL_NONE)
		{
			RBFormat key(format, width, height, fsaa);
			RenderBufferMap::iterator it = mRenderBufferMap.find(key);
			if (it != mRenderBufferMap.end())
			{
				retval.buffer = it->second.buffer;
				retval.zoffset = 0;
				retval.numSamples = fsaa;
				++it->second.refCount;
			}
			else
			{
				GLES2RenderBuffer* rb = new GLES2RenderBuffer(format, width, height, fsaa);
				mRenderBufferMap[key] = RBRef(rb);
				retval.buffer = rb;
				retval.zoffset = 0;
				retval.numSamples = fsaa;
			}
		}
		
		return retval;
	}
	
	void GLES2FBOManager::requestRenderBuffer(const GLES2SurfaceDesc& surface)
	{
		if (surface.buffer == 0)
			return;
		RBFormat key(surface.buffer->getGLFormat(), surface.buffer->getWidth(), surface.buffer->getHeight(), surface.numSamples);
		RenderBufferMap::iterator it = mRenderBufferMap.find(key);
		assert(it != mRenderBufferMap.end());
		if (it != mRenderBufferMap.end())
		{
			assert(it->second.buffer == surface.buffer);
			++it->second.refcount;
		}
	}
	
	void GLES2FBOManager::releaseRenderBuffer(const GLES2SurfaceDesc& surface)
	{
		if (surface.buffer == 0)
			return;
		RBFormat key(surface.buffer->getGLFormat(), surface.buffer->getWidth(), surface.buffer->getHeight(), surface.numSamples);
		RenderBufferMap::iterator it = mRenderBufferMap.find(key);
		if (it != mRenderBufferMap.end())
		{
			--it->second.refcount;
			if (it->second.refcount == 0)
			{
				delete it->second.buffer;
				mRenderBufferMap.erase(it);
			}
		}
	}
}
